/*
 * DataLinkLaye.cpp
 *
 *  Created on: Mar 14, 2013
 *      Author: Xi
 */

#include "DataLinkLayer.h"
#include "Utilities.h"
#include "PhysicalLayer.h"
#include "ApplicationLayer.h"
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include"Client.h"
#include<sstream>
#include<math.h>
#include <fstream>

Client c;
int expected_ACK = 1;
pthread_t recvThread; /* recv thread */
pthread_t queryThread; /* send thread */
pthread_t timerThread[8];
pthread_t dlSendThread;


int ACKIndex = 1; /*index for next frame's header*/
vector<string> window; /*if the window is full, put frame in queue*/
vector<string> frameChunk; /*store frame chunks and pack up to packet then pass to application layer*/
vector<string> sendQueue;
vector<string> queue; /*queue for send buffer*/
bool recvFlag = true; /*recv only called once, then runs recursively*/
bool sendFlag = true;
bool timerFlag = false;
int receiverWindowIndex = 0;
int recvfd; /*fd for socket*/
int fdptr; /*pointer to fd*/
int errorRate = -99; /*error rate*/
int errorCount = 0; /* allowed specifed number of errors in a packet */
int errorSpace = 8;

/**
 * Default constructor, just instantiate object
 */

DataLinkLayer::DataLinkLayer() {

}

/**
 * Default destructor
 */
DataLinkLayer::~DataLinkLayer() {

}

/**
 * Return payloads from a set of frames
 * @param frames
 * @return 
 */
string getPayload(vector<string> frames) {
    string packet;
    int i = 0;
    for (i = 0; i < frames.size(); i++) {
        string payload;
        payload = frames.at(i).substr(7);
        payload = payload.substr(0, payload.size() - 3);
        packet += payload;
    }
    return packet;
}

/**
 * Return payload from a frame
 * @param frame
 * @return 
 */
string getPayload(string frame) {

    string packet = "";
    string payload = "";
    payload = frame.substr(7);
    payload = payload.substr(0, payload.size() - 3);
    packet += payload;

    return packet;
}

/**
 * Return ACK from a frame
 * @param frame
 * @return 
 */
string getACK(string frame) {

    string ack = frame.substr(5, 2);
    return ack;
}

/**
 * Return length in header from a frame
 * @param frame
 * @return 
 */
string getLength(string frame) {
    string length = frame.substr(1, 2);
    return length;
}

/**
 * Return sequence from frame
 * @param frame
 * @return 
 */
string getSeq(string frame) {
    string seq = frame.substr(3, 2);
    return seq;
}

void* query(void *strPtr) {
    string packet = sendQueue.at(0);
    sendQueue.erase(sendQueue.begin());
    ApplicationLayer app;
    app.toCmd(packet, " ", recvfd);
}

/**
 * Timer for frame resend event, if received expecting ACK, cancel the thread
 * @param ackPtr
 * @return 
 */
void* startTimer(void* ackPtr) {

    int ack = *((int*) ackPtr);

    if (ack != 1)
        pthread_join(timerThread[ack - 1 ], &ackPtr);

    usleep(50000); //30ms

    /*re-send frame*/
    PhysicalLayer phys;
    int i = ack - expected_ACK;

    timerFlag = true;

    vector<string> windowCopy;
    for (int j = 0; j < window.size(); j++)
        windowCopy.push_back(window.at(j));

    for (int j = i; j < windowCopy.size(); j++) {
        if (j < window.size() && j >= 0) {
            cout << "Resend frame " << j << " with ACK: " << getACK(windowCopy.at(j)) << "  :  Current window size " << windowCopy.size() << endl;
            phys.fromDLLayer(windowCopy.at(j), fdptr);
        } else
            break;
    }

    usleep(500000); //30ms
    /*resend twice*/
    for (int j = i; j < windowCopy.size(); j++) {
        if (j < window.size() && j >= 0) {
            cout << "Resend frame " << j << " with ACK: " << getACK(windowCopy.at(j)) << "  :  Current window size " << windowCopy.size() << endl;
            phys.fromDLLayer(windowCopy.at(j), fdptr);
        } else
            break;
    }
    cout << "Connection failed, may blocked by system as overflow?!" << endl;
    pthread_exit(NULL);
}

/**
 * return random number in range min - max
 * @param min
 * @param max
 * @return 
 */
int randomGenerator(int min, int max) {
    int random, max_value = 100, min_value = 0;
    random = rand() % ((max_value + min_value) + min_value);

    return random;
}

/**
 * Damage ACKs, replace ACK to 99
 * @param s
 * @return 
 */
string damageACK(string s) {
    Utilities u;
    u.echoDebugInfo("Will damage the ACK");
    /*replace ACK to 99*/
    string f = s.replace(5, 2, "99");

    return f;
}

/**
 * Damage ACKs, replace ACK to 99
 * @param s
 * @return 
 */
string damageFrame(string s) {
    Utilities u;
    u.echoDebugInfo("Will damage the frame");
    /*replace checksum to 99*/
    string f = s.replace(s.length() - 4, 2, "ee");

    return f;
}

/**
 * Set error rate, damage either ACK or checksum if random number less than error rate
 * @param v
 */
void DataLinkLayer::setErrorRate(int v) {
    if (v > 0)
        errorRate = v;
    errorCount = 0;
}

/**
 * Data link layer send function, runs separately and recursively
 * @param fdptr
 * @return 
 */
void* dl_send(void *fdptr) {
    bool run = true;
    while (run) {
        if (!queue.empty() && window.size() < 8 && timerFlag == false) {
            window.push_back(queue.at(0));

            string frame = queue.at(0);

            /*damage frame*/
            string dmFrame;
            int random = randomGenerator(0, 100);
            if (random < errorRate && errorCount < 5 && errorSpace > 8) {
                errorSpace = 0;
                errorCount++;
                cout << "Sent out an damaged frame with ACK" << getACK(frame) << endl;
                if (random < (int) (errorRate / 2))
                    dmFrame = damageACK(frame);
                else
                    dmFrame = damageFrame(frame);
                /*start timer*/
                string ackStr = getACK(frame);
                int currentACK = atoi(ackStr.c_str());
                //currentACK++;
                pthread_create(&timerThread[currentACK - 1], 0, &startTimer, (void*) &currentACK);
                pthread_detach(timerThread[currentACK - 1]);
                PhysicalLayer phy;
                phy.fromDLLayer(dmFrame, recvfd); //send  out  frame
            } else {
                errorSpace++;
                string ackStr = getACK(frame);
                int currentACK = atoi(ackStr.c_str());
                pthread_create(&timerThread[currentACK - 1], 0, &startTimer, (void*) &currentACK);
                pthread_detach(timerThread[currentACK - 1]);

                PhysicalLayer phy;
                phy.fromDLLayer(frame, recvfd);
            }
            queue.erase(queue.begin());

        }
        if (window.size() == 8) {
            timerFlag = true;
            cout << "Window is full!" << endl;
            void* ptr;
            pthread_join(timerThread[expected_ACK - 1], &ptr);
        }
        usleep(5000);
        continue;
    }
}

/**
 * Key function for data link layer- it runs recursively to receiving frame from physical layer, then judge whether it's ACKs or frames;
 * For ACKs- if arriving ACK equals to expected ACK, expand expected ACK, else goto next loop wait for timer event resend the frame;
 * For frames- first do checksum, if the frame is correct return the ACK back, then pass payload to application layer, else simply end the code.
 * @param fdptr
 * @return 
 */
void* dl_recv(void *fdptr) {

    Utilities u;
    u.echoDebugInfo("Inside DataLinkLayer dl_recv(), runs recursively");
    recvfd = *((int*) fdptr);
    int fd = recvfd;

    PhysicalLayer phys;
    bool run = true;

    while (run) {
        string frame = phys.toDLLayer(fd);

        if (frame.length() <= 2 && frame.length() > 0) {
            /*arrive frame is ACK*/
            int ACK = atoi(frame.c_str());
            cout << "Received : Expected  <  --  >  " << ACK << " : " << expected_ACK << endl;
            if (ACK == expected_ACK) {
                /*update expected ACK*/
                if (expected_ACK == 8) {
                    expected_ACK = 1;
                } else
                    expected_ACK++;

                /*stop timer*/
                timerFlag = false;
                pthread_cancel(timerThread[ACK - 1]);

                /*slide window*/
                window.erase(window.begin());

                continue;
            } else
                continue;
        } else if (frame.length() >= 6) {
            int ACK = atoi(frame.c_str());
            int l = frame.length();
            int i, j;
            unsigned CRC, gx;
            CRC = 0;
            char *crcCheck; //checksum in hex
            char *crcRecv; //checksum in frame 
            crcCheck = (char*) malloc(2);
            crcRecv = (char*) malloc(2);
            gx = 0x107;

            crcRecv[0] = frame[l - 3];
            crcRecv[1] = frame[l - 2];
            crcRecv[2] = '\0';
            for (i = 0; i < l - 3; i++) //frame, from the first byte to last before checksum byte 	
            {
                CRC ^= frame[i];
                for (j = 0; j < 8; j++) {
                    if (CRC & 0x80)
                        CRC = (CRC << 1) ^ gx;
                    else
                        CRC = (CRC << 1);
                }
                CRC &= 0xff;
            }
            sprintf(crcCheck, "%02x", CRC);

            if (strcmp(crcCheck, crcRecv) != 0 || ACK > 8 || ACK < -1) {
                //        u.echoDebugInfo("Inside DataLinkLayer: receive a damaged frame");
                cout << "CRC error! Receive a damaged frame!!" << endl;
                free(crcCheck);
                free(crcRecv);
                continue;
            } else {
                free(crcCheck);
                free(crcRecv);

                /*get frame length*/
                string lengthStr = getLength(frame);
                int length = atoi(lengthStr.c_str());
                u.echoDebugInfo("Inside DataLinkLayer: received a frame");

                if (length == 3) {
                    if (frameChunk.size() < 3 && frameChunk.size() != 0) {

                        string previousACKStr = getACK(frameChunk.at(frameChunk.size() - 1));
                        int previousACK = atoi(previousACKStr.c_str());
                        string currentACKStr = getACK(frame);
                        int currentACK = atoi(currentACKStr.c_str());

                        string previousSeqStr = getSeq(frameChunk.at(frameChunk.size() - 1));
                        int previousSeq = atoi(previousSeqStr.c_str());
                        string currentSeqStr = getSeq(frame);
                        int currentSeq = atoi(currentSeqStr.c_str());

                        int dif = currentACK - previousACK;
                        int dif2 = currentSeq - previousSeq;
                        int dif3 = currentACK - receiverWindowIndex;
                        if ((dif == 1 || dif == -7) && (dif2 == 1) && (dif3 == 1 || dif3 == -7)) {

                            receiverWindowIndex = currentACK;
                            /*return ACK*/
                            string ack = getACK(frame);
                            phys.fromDLLayer(ack, fd);

                            frameChunk.push_back(frame);
                            if (frameChunk.size() == 3) {

                                string packet = getPayload(frameChunk);
                                frameChunk.clear();
                                sendQueue.push_back(packet);
                                pthread_create(&queryThread, NULL, &query, (void*) &packet);
                                pthread_detach(queryThread);
                                void *ptr;
                                pthread_join(queryThread, &ptr);
                                continue;
                            }
                        } else {
                            cout << "Unexpted frame " << currentACK << endl;
                        }
                        continue;
                    } else {
                        string seqStr = getSeq(frame);
                        int seq = atoi(seqStr.c_str());

                        string ackStr = getACK(frame);
                        int ACK = atoi(ackStr.c_str());
                        int dif3 = ACK - receiverWindowIndex;
                        if (seq == 1 && (dif3 == 1 || dif3 == -7)) {
                            receiverWindowIndex = ACK;
                            /*return ACK*/
                            string ack = getACK(frame);
                            phys.fromDLLayer(ack, fd);
                            frameChunk.push_back(frame);
                            continue;
                        }
                        continue;
                    }
                } else if (length == 2) {
                    if (frameChunk.size() < 2 && frameChunk.size() != 0) {

                        string previousACKStr = getACK(frameChunk.at(frameChunk.size() - 1));
                        int previousACK = atoi(previousACKStr.c_str());
                        string currentACKStr = getACK(frame);
                        int currentACK = atoi(currentACKStr.c_str());

                        string previousSeqStr = getSeq(frameChunk.at(frameChunk.size() - 1));
                        int previousSeq = atoi(previousSeqStr.c_str());
                        string currentSeqStr = getSeq(frame);
                        int currentSeq = atoi(currentSeqStr.c_str());

                        int dif = currentACK - previousACK;
                        int dif2 = currentSeq - previousSeq;
                        int dif3 = currentACK - receiverWindowIndex;
                        if ((dif == 1 || dif == -7) && (dif2 == 1) && (dif3 == 1 || dif3 == -7)) {
                            receiverWindowIndex = currentACK;
                            /*return ACK*/
                            string ack = getACK(frame);
                            phys.fromDLLayer(ack, fd);

                            frameChunk.push_back(frame);
                            if (frameChunk.size() == 2) {

                                string packet = getPayload(frameChunk);
                                frameChunk.clear();
                                sendQueue.push_back(packet);
                                pthread_create(&queryThread, NULL, &query, (void*) &packet);
                                pthread_detach(queryThread);
                                void *ptr;
                                pthread_join(queryThread, &ptr);
                                continue;
                            }
                        } else {
                            cout << "Unexpted frame " << currentACK << endl;
                        }
                        continue;
                    } else {
                        string seqStr = getSeq(frame);
                        int seq = atoi(seqStr.c_str());
                        string ackStr = getACK(frame);
                        int ACK = atoi(ackStr.c_str());
                        int dif3 = ACK - receiverWindowIndex;
                        if (seq == 1 && (dif3 == 1 || dif3 == -7)) {
                            receiverWindowIndex = ACK;
                            /*return ACK*/
                            string ack = getACK(frame);
                            phys.fromDLLayer(ack, fd);
                            frameChunk.push_back(frame);
                        }
                        continue;
                    }
                } else if (length == 1) {

                    string seqStr = getSeq(frame);
                    int seq = atoi(seqStr.c_str());

                    string ackStr = getACK(frame);
                    int ACK = atoi(ackStr.c_str());
                    int dif3 = ACK - receiverWindowIndex;
                    if (seq == 1 && (dif3 == 1 || dif3 == -7)) {
                        receiverWindowIndex = ACK;
                        /*return ACK*/
                        string ack = getACK(frame);
                        phys.fromDLLayer(ack, fd);
                        string packet = getPayload(frame);
                        sendQueue.push_back(packet);
                        pthread_create(&queryThread, NULL, &query, (void*) &packet);
                        pthread_detach(queryThread);
                        void *ptr;
                        pthread_join(queryThread, &ptr);
                        continue;
                    }
                    continue;
                }
            }
            continue;
        }
        continue;
    }
    pthread_exit(NULL);
}

/**
 * Receive packet from Application layer, first break into several frames, then add header for each frame, finally do stuffing and pass them to physical layer.
 * Here we use go-back-8.
 * @param packet
 * @param fd
 * @return 
 */
string DataLinkLayer::fromAppLayer(string packet, int fd) {

    /*debug info*/
    utl.echoDebugInfo("Inside DataLinkLayer::fromAppLayer()");

    /*receive in parallel*/
    fdptr = fd;
    if (recvFlag == true) {
        pthread_create(&recvThread, NULL, &dl_recv, (void*) &fdptr);
        pthread_detach(recvThread);
        recvFlag = false;
    }

    /*send in parallel*/
    if (sendFlag == true) {
        pthread_create(&dlSendThread, NULL, &dl_send, (void*) &fdptr);
        pthread_detach(dlSendThread);
        sendFlag = false;
    }
    /* header */
    vector<string> frames;
    frames = utl.splitString(packet, ' ', 90);
    if (frames.empty())
        return "a";
    else {
        int i = 0;
        for (i = 0; i < frames.size(); i++) {
            string header;
            stringstream s;
            int size = frames.size();
            s << size;
            string len = s.str();

            header = "~0" + len + "0";
            stringstream ss;
            int v = i + 1;
            ss << v;
            string seq = ss.str();
            header += seq; //5 bytes for, stuffing byte, length and sequence

            // append ACK
            header += "0";
            stringstream ss1;
            ss1 << ACKIndex;
            string ACK = ss1.str();
            header += ACK; //ACK

            /*expand index*/
            if (ACKIndex == 8)
                ACKIndex = 1;
            else
                ACKIndex++;

            string temp = frames.at(i);
            string frame = header;
            frame += temp; // head and payload

            /*footer*/

            int l;
            unsigned int CRC, gx;
            CRC = 0;
            char *crc; //hex crc
            crc = (char *) malloc(2);
            gx = 0x107; //polynomial G(X)
            l = frame.length();
            int i, j;
            for (i = 0; i < l; i++) {
                CRC ^= frame[i];
                for (j = 0; j < 8; j++) {
                    if (CRC & 0x80) {
                        CRC = (CRC << 1) ^ gx;
                    } else {
                        CRC <<= 1;
                    }
                }
            }

            sprintf(crc, "%02x", CRC);
            string crcStr(crc, 2);
            frame += crcStr;
            frame += "~"; // stuffing
            /*store frame in queue*/
            queue.push_back(frame);
        }
        return "a";
    }
}

/**
 * Receive function in data link layer, since we want run recv() recursively and separately from main thread, this function only called once by server when new client connected
 * @param fd
 * @return 
 */
string DataLinkLayer::toAppLayer(int fd) {
    utl.echoDebugInfo("Inside DataLinkLayer::toAppLayer()");
    fdptr = fd;
    int err;
    cout << "fd--------------" << fd << endl;
    err = pthread_create(&recvThread, NULL, &dl_recv, (void*) &fdptr);
    pthread_detach(recvThread);
    recvFlag = false;

    return "b";
}

void DataLinkLayer::sendPic(string param) {
    param += ".txt";
    ifstream infile(param.c_str());
    // get size of file
    infile.seekg(0, infile.end);
    int size = infile.tellg();
    infile.seekg(0);


    char* buffer;
    buffer = (char*) malloc(size);
    infile.read(buffer, size);
    infile.close();

    ApplicationLayer app;
    app.fromPic(buffer, recvfd, size);
    free(buffer);
}
